package org.springbus.ff.node;

import org.apache.commons.io.FileUtils;
import org.springbus.ff.animate.FFTransition;
import org.springbus.ff.conf.FFBaseConf;
import org.springbus.ff.core.FFBase;
import org.springbus.ff.core.FFCommand;
import org.springbus.ff.core.FFContext;
import org.springbus.ff.filter.Filter;
import org.springbus.ff.util.FFmpegUtil;
import org.springbus.ff.util.ImageUtils;
import org.springbus.ff.util.Utils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class FFScene extends FFCon {

    private String pathId;
    private float duration;
    private String directory;
    private FFContext context;


    private FFTransition transition;

    private FFNode background;

    private String filepath;

    public FFScene() {
        this(new FFBaseConf());
    }


    public FFScene(FFBaseConf conf) {
        //super({ type: 'scene', ...conf });
        super(conf,"scene");
        this.percent = 0;
        this.duration = 10;
        this.directory = "";
        this.context = null;
        this.command = null;
        this.transition = null;
        this.pathId = Utils.uid();
        this.setBgColor("#000000");
        addBlankImage();
    }

    public void addBlankImage() {
        String blank = conf.getCacheDir() + UUID.randomUUID().toString() + ".png";
        ImageUtils.createEmptyPic(10, 10, conf.getColor(), blank);
        FFBaseConf cf = new FFBaseConf();
        cf.setPath(blank);
        this.addChild(new FFImage(cf));
    }


    /**
     * Set the time the scene stays in the scree
     *
     * @param {number} duration - the time the scene
     * @public
     */
    public void setDuration(float duration) {
        if (this.background != null) {
            this.background.setDuration(duration);
        }
        this.duration = duration;
    }

    /**
     * Set scene transition animation
     *
     * @param {string|object} name - transition animation name or animation conf object
     * @param {number}        duration - transition animation duration
     * @param {object}        params - transition animation params
     * @public
     */
    public void setTransition(Object name, float duration) {
        String curName = "";
        if (name instanceof FFBaseConf) {
            FFBaseConf cf = (FFBaseConf) name;
            curName = cf.getName();
            duration = cf.getTansDuration();
        } else if (name instanceof String) {
            curName = (String) name;
        }
        FFBaseConf baseConf = new FFBaseConf();
        baseConf.setName(curName);
        baseConf.setTansDuration(duration);
        this.transition = new FFTransition(baseConf);
    }

    /**
     * Set background color
     *
     * @param {string} bgcolor - background color
     * @public
     */
    public void setBgColor(String color) {
        if (this.background != null) {
            this.removeChild(this.background);
        }
        conf.setColor(color);
        this.background = new FFBackGround(conf);
        this.addChildAt(this.background, 0);
    }

    public String getFile() {
        return this.filepath;
    }

    public Float getNormalDuration() {
        int transTime = 0;
        if (this.transition != null) {
            transTime = this.transition.duration;
        }
        return this.duration - transTime;
    }

    public String createFilepath() {
        String cacheDir = conf.getCacheDir().replace("\\", "/");
        String format = conf.getCacheFormat();
        String directory = cacheDir + "/" + pathId;
        String filepath = directory + "/" + id + "." + format;
        new File(directory).mkdirs();

        this.filepath = filepath;
        this.directory = directory;
        return filepath;
    }

    // about command
    public void createNewCommand() {
        int threads = conf.getThreads();
        boolean upStreaming = conf.isUpStreaming();
        FFCommand command = FFmpegUtil.createCommand(threads);
        if (!upStreaming) {
            command.setDuration(this.duration);
        }

        this.command = command;
        this.context = new FFContext();
    }

    public FFCommand toCommand() {
        FFCommand c = this.command;
        this.addNodesInputCommand(c);
        this.toFiltersCommand(c);
        this.addCommandOptions(c);
        this.addCommandOutputs(c);
        this.addNodesOutputCommand(c);
        return command;
    }

    public void start() {
        this.createNewCommand();
        this.toCommand();
        this.command.run();
    }

    public void addCommandOptions(FFCommand command) {

        boolean upStreaming = conf.isUpStreaming();

        if (upStreaming) {
            FFmpegUtil.addDefaultOptions(command, conf, true);
        } else {
            FFmpegUtil.addDefaultOptions(command, conf, false);
        }

        int fps = conf.getFps();
        if (fps != 25) {
            command.outputFPS(fps);
        }
        for (FFBase child : children) {
            child.addOptions(command);
        }
    }

    public void addCommandOutputs(FFCommand command) {

        String output = conf.getOutput();
        boolean upStreaming = conf.isUpStreaming();

        if (upStreaming) {
            filepath = output;
            List<String> f = new ArrayList<>();
            f.add("-f");
            f.add("flv");
            command.outputOptions(f);
        } else {
            filepath = this.createFilepath();
        }
        command.output(filepath);
    }

    public void deleteCacheFile() throws IOException {
        boolean debug = conf.isDebug();
        if (!debug && this.directory != null) {
            FileUtils.deleteDirectory(new File(directory));
        }
    }

    // addInputs
    public void addNodesInputCommand(FFCommand command) {
        for (FFBase child : children) {
            child.addInput(command);
        }

    }

    // addOutputs
    public void addNodesOutputCommand(FFCommand command) {
        for (FFBase child : children) {
            child.addOutput(command);
        }
    }

    // filters toCommand
    public void toFiltersCommand(FFCommand command) {
        List<Object> filters = this.concatFilters();
        command.complexFilter(filters, this.context.input);
    }

    public Filter toTransFilter(int offset) {
        return this.transition.toFilter(offset);
    }

    public void fillTransition() {
        if (this.transition != null) {
            FFBaseConf cf = new FFBaseConf();
            cf.setName("fade");
            cf.setTansDuration(0.5f);
            this.setTransition(cf, 0.5f);
        }
    }

    /**
     * Combine various filters as ffmpeg parameters
     * &#064;private
     */
    public List<Object> concatFilters() {
        for (FFBase child : children) {
            List<Object> concatFilters = child.concatFilters(this.context);
            if (!concatFilters.isEmpty()) {
                this.filters.addAll(concatFilters);
            }
        }
        return this.filters;
    }

    public int getTotalFrames() {
        return (int) (conf.getFps() * this.duration);
    }

    public void addAudio() {
        String audio = conf.getAudio();
        boolean loop = conf.isAudioLoop();
        if (audio != null) {
            FFBaseConf cf = new FFBaseConf();
            cf.setAudio(audio);
            cf.setAudioLoop(loop);
            this.addChild(new FFAudio(cf));
        }
    }

    public boolean isReady() {
        int readyIndex = 0;
        for (FFBase child : this.children) {
            if (child.isReady())
                readyIndex++;
            if (readyIndex >= this.children.size()) {
                break;
            }
        }
        return true;
    }

    public void destroy() {
        //FFmpegUtil.destroy(this.command);
        super.destroy();
        this.transition.destroy();
        this.transition = null;
        this.context = null;
        this.command = null;
    }

}
